package com.start.workflow.core;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.start.common.tools.StartStreamTools;
import org.activiti.bpmn.constants.BpmnXMLConstants;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.*;
import org.apache.commons.lang3.StringUtils;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class StartActTools {


    public static List<UserTask> getNextUserTasks(BpmnModel bpmnModel) {
        return getNextUserTasks(bpmnModel, null);
    }

    public static List<UserTask> getNextUserTasks(BpmnModel bpmnModel, Map<String, Object> processVariables) {
        List<UserTask> result = new ArrayList<>();
        List<String> elementTypes = new ArrayList<>();
        elementTypes.add(BpmnXMLConstants.ELEMENT_TASK_USER);
        bpmnModelFindElement(bpmnModel, null, null, elementTypes, processVariables, (level, elementType, flowElement) -> {
            if (level < 1) {
                return true;
            }
            if (level == 1) {
                result.add((UserTask) flowElement);
                return true;
            }
            return false;
        });
        return result;
    }

    public static List<UserTask> getNextUserTasksByStartKey(BpmnModel bpmnModel, String currNodeKey) {
        return getNextUserTasksByStartKey(bpmnModel, currNodeKey, null);
    }

    public static List<UserTask> getNextUserTasksByStartKey(BpmnModel bpmnModel, String currNodeKey, Map<String, Object> processVariables) {
        List<UserTask> result = new ArrayList<>();
        List<String> elementTypes = new ArrayList<>();
        elementTypes.add(BpmnXMLConstants.ELEMENT_TASK_USER);
        bpmnModelFindElement(bpmnModel, null, currNodeKey, elementTypes, processVariables, (level, elementType, flowElement) -> {
            if (level < 1) {
                return true;
            }
            if (level == 1) {
                result.add((UserTask) flowElement);
                return true;
            }
            return false;
        });
        return result.stream().filter(StartStreamTools.distinctByKey(BaseElement::getId)).collect(Collectors.toList());
    }

    public static void bpmnModelFindElement(BpmnModel bpmnModel, String procDefKey, Collection<String> elementTypes, Map<String, Object> processVariables, BpmnElementFind elementFind) {
        if (elementTypes.isEmpty()) {
            return;
        }
        bpmnModelFindElement(bpmnModel, procDefKey, null, elementTypes, processVariables, elementFind);
    }

    /**
     * 流程元素查找
     *
     * @param bpmnModel        流程model
     * @param procDefKey       流程定义key(子流程)
     * @param startDefKey      起始接地点
     * @param elementTypes     查找的流程类型
     * @param processVariables 流程变量
     * @param elementFind      回调函数
     */
    public static void bpmnModelFindElement(BpmnModel bpmnModel, String procDefKey, String startDefKey, Collection<String> elementTypes, Map<String, Object> processVariables, BpmnElementFind elementFind) {
        if (elementTypes.isEmpty()) {
            return;
        }
        Process process = StringUtils.isAllBlank(procDefKey) ? bpmnModel.getMainProcess() : bpmnModel.getProcess(procDefKey);
        processFindElement(process, startDefKey, elementTypes, processVariables, elementFind);
    }


    public static void processFindElement(Process process, Collection<String> elementTypes, BpmnElementFind elementFind) {
        processFindElement(process, null, elementTypes, null, elementFind);
    }

    public static void processFindElement(Process process, Collection<String> elementTypes, Map<String, Object> processVariables, BpmnElementFind elementFind) {
        processFindElement(process, null, elementTypes, processVariables, elementFind);
    }

    public static void processFindElement(Process process, String startDefKey, Collection<String> elementTypes, Map<String, Object> processVariables, BpmnElementFind elementFind) {
        FlowElement flowElement = StringUtils.isAllBlank(startDefKey) ? process.getInitialFlowElement() : process.getFlowElement(startDefKey);
        if (!(flowElement instanceof FlowNode)) {
            return;
        }
        FlowNode rootNode = (FlowNode) flowElement;
        flowElementFindElement(rootNode, elementTypes, processVariables, 0, elementFind);
    }

    /**
     * 流程模型元素查找
     *
     * @param rootElement      根节点
     * @param elementTypes     查找的类型
     * @param processVariables 流程变量
     * @param level            当前正在查找的层级
     * @param elementFind      查找到元素后回掉
     */
    private static void flowElementFindElement(FlowNode rootElement, Collection<String> elementTypes, Map<String, Object> processVariables, int level, BpmnElementFind elementFind) {
        String simpleName = StrUtil.lowerFirst(rootElement.getClass().getSimpleName());
        if (elementTypes.contains(simpleName)) {
            if (!elementFind.find(level, simpleName, rootElement)) {
                return;
            }
        }
        List<SequenceFlow> outgoingFlows = rootElement.getOutgoingFlows();
        if (outgoingFlows.isEmpty()) {
            return;
        }
        for (SequenceFlow sequenceFlow : outgoingFlows) {
            String conditionExpression = getConditionExpression(sequenceFlow);
            //检查流程变量，以及线上是否配置条件表达式
            if (processVariables != null && processVariables.isEmpty() && StringUtils.isNotBlank(conditionExpression)) {

            }
            FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement();
            if (targetFlowElement == null) {
                continue;
            }
            if (!(targetFlowElement instanceof FlowNode)) {
                continue;
            }

            FlowNode curFlowNode = (FlowNode) targetFlowElement;
            flowElementFindElement(curFlowNode, elementTypes, processVariables, level + 1, elementFind);
        }
    }

    private static String getConditionExpression(SequenceFlow sequenceFlow) {
        String conditionExpression = null;
//        if (Context.getProcessEngineConfiguration().isEnableProcessDefinitionInfoCache()) {
//            ObjectNode elementProperties = Context.getBpmnOverrideElementProperties(sequenceFlow.getId(), execution.getProcessDefinitionId());
//          conditionExpression = getActiveValue(sequenceFlow.getConditionExpression(), DynamicBpmnConstants.SEQUENCE_FLOW_CONDITION, elementProperties);
//        } else {
//            conditionExpression = sequenceFlow.getConditionExpression();
//        }
        return conditionExpression;
    }

    private static String getActiveValue(String originalValue, String propertyName, ObjectNode elementProperties) {
        String activeValue = originalValue;
        if (elementProperties != null) {
            JsonNode overrideValueNode = elementProperties.get(propertyName);
            if (overrideValueNode != null) {
                if (overrideValueNode.isNull()) {
                    activeValue = null;
                } else {
                    activeValue = overrideValueNode.asText();
                }
            }
        }
        return activeValue;
    }

    public static void getBpmnModelVariables(BpmnModel bpmnModel, String procDefKey) {
        Process process = bpmnModel.getProcess(procDefKey);
        Collection<FlowElement> flowElements = process.getFlowElements();
        for (FlowElement flowElement : flowElements) {
            if (!(flowElement instanceof SequenceFlow)) {
                continue;
            }
            SequenceFlow sequenceFlow = (SequenceFlow) flowElement;
            String expression = sequenceFlow.getConditionExpression();
            if (StringUtils.isAllBlank(expression)) {
                continue;
            }

        }
    }

    public static BpmnModel getBpmnModelByBytes(byte[] bytes) throws XMLStreamException {
        return getBpmnModelByBytes(bytes, StandardCharsets.UTF_8);
    }

    public static BpmnModel getBpmnModelByBytes(byte[] bytes, Charset charset) throws XMLStreamException {
        if (charset == null) {
            charset = StandardCharsets.UTF_8;
        }
        String xml = new String(bytes, charset);
        return getBpmnModelByXml(xml);
    }

    public static BpmnModel getBpmnModelByXml(String xml) throws XMLStreamException {
        XMLInputFactory factory = XMLInputFactory.newFactory();
        Reader reader = new StringReader(xml);
        XMLStreamReader streamReader = factory.createXMLStreamReader(reader);
        BpmnXMLConverter converter = new BpmnXMLConverter();
        return converter.convertToBpmnModel(streamReader);
    }

    /**
     * 流程节点跳转
     *
     * @param bpmnModel  流程模型定义
     * @param curTaskKey 当前任务定义key
     * @param tarTaskKey 目标任务定义key
     * @param apply      应用跳转
     */
    public static void bpmnModeJumpUserTask(BpmnModel bpmnModel, String curTaskKey, String tarTaskKey, StartActTaskJumpApply apply) {
        FlowElement curFlowElement = bpmnModel.getFlowElement(curTaskKey);
        if (curFlowElement == null) {
            throw new RuntimeException("当前任务节点在流程定义中不存在无法进行跳转：" + curTaskKey);
        }
        if (!(curFlowElement instanceof UserTask)) {
            throw new RuntimeException("当前环节不是一个有效的用户任务无法跳转：" + curTaskKey);
        }
        UserTask curUserTask = (UserTask) curFlowElement;
        FlowElement flowElement = bpmnModel.getFlowElement(tarTaskKey);
        if (flowElement == null) {
            throw new RuntimeException("目标流程任务节点定义key：" + tarTaskKey + "在流程定义中不存在");
        }
        UserTask targetUserTask = null;
        if (!(flowElement instanceof UserTask)) {
            throw new RuntimeException("目标流程任务节点不是一个有效的用户任务无法跳转：" + tarTaskKey);
        }
        targetUserTask = (UserTask) flowElement;
        List<SequenceFlow> oldOutgoingFlows = curUserTask.getOutgoingFlows();// 记录旧的方向
        ArrayList<SequenceFlow> newOutgoingFlows = new ArrayList<>();
        SequenceFlow sequenceFlow = new SequenceFlow();
        sequenceFlow.setId("sequenceFlow_temp" + IdUtil.simpleUUID());
        sequenceFlow.setSourceFlowElement(curFlowElement);
        sequenceFlow.setTargetFlowElement(targetUserTask);
        newOutgoingFlows.add(sequenceFlow);//设置新的方向
        curUserTask.setOutgoingFlows(newOutgoingFlows);
        try {
            apply.apply();
        } catch (Exception e) {
            throw e;
        } finally {
            //还原旧的方向
            curUserTask.setOutgoingFlows(oldOutgoingFlows);
        }
    }

}
